# frozen_string_literal: true

module Email
  class AuthenticationResults
    VERDICT = Enum.new(:gray, :pass, :fail, start: 0)

    def initialize(headers)
      @authserv_id = SiteSetting.email_in_authserv_id
      @headers = headers
      @verdict = :gray if @authserv_id.blank?
    end

    def results
      @results ||=
        Array(@headers)
          .map { |header| parse_header(header.to_s) }
          .filter { |result| @authserv_id.blank? || @authserv_id == result[:authserv_id] }
    end

    def action
      @action ||= calc_action
    end

    def verdict
      @verdict ||= calc_verdict
    end

    private

    def calc_action
      if verdict == :fail
        :enqueue
      else
        :accept
      end
    end

    def calc_verdict
      VERDICT[calc_dmarc]
    end

    def calc_dmarc
      verdict = VERDICT[:gray]
      results.each do |result|
        result[:resinfo].each do |resinfo|
          if resinfo[:method] == "dmarc"
            v = VERDICT[resinfo[:result].to_sym].to_i
            verdict = v if v > verdict
          end
        end
      end
      verdict = VERDICT[:gray] if SiteSetting.email_in_authserv_id.blank? &&
        verdict == VERDICT[:pass]
      verdict
    end

    def parse_header(header)
      # based on https://tools.ietf.org/html/rfc8601#section-2.2
      cfws = /\s*(\([^()]*\))?\s*/
      value = /(?:"([^"]*)")|(?:([^\s";]*))/
      authserv_id = value
      authres_version = /\d+#{cfws}?/
      no_result = /#{cfws}?;#{cfws}?none/
      keyword = /([a-zA-Z0-9-]*[a-zA-Z0-9])/
      authres_payload =
        /\A#{cfws}?#{authserv_id}(?:#{cfws}#{authres_version})?(?:#{no_result}|([\S\s]*))/

      method_version = authres_version
      method = %r{#{keyword}\s*(?:#{cfws}?/#{cfws}?#{method_version})?}
      result = keyword
      methodspec = /#{cfws}?#{method}#{cfws}?=#{cfws}?#{result}/
      reasonspec = /reason#{cfws}?=#{cfws}?#{value}/
      resinfo = /#{cfws}?;#{methodspec}(?:#{cfws}#{reasonspec})?(?:#{cfws}([^;]*))?/

      ptype = keyword
      property = value
      pvalue = /#{cfws}?#{value}#{cfws}?/
      propspec = /#{ptype}#{cfws}?\.#{cfws}?#{property}#{cfws}?=#{pvalue}/

      authres_payload_match = authres_payload.match(header)
      parsed_authserv_id = authres_payload_match[2] || authres_payload_match[3]
      resinfo_val = authres_payload_match[-1]

      if resinfo_val
        resinfo_scan = resinfo_val.scan(resinfo)
        parsed_resinfo =
          resinfo_scan.map do |x|
            {
              method: x[2],
              result: x[8],
              reason: x[12] || x[13],
              props:
                x[-1]
                  .scan(propspec)
                  .map { |y| { ptype: y[0], property: y[4], pvalue: y[8] || y[9] } },
            }
          end
      end

      { authserv_id: parsed_authserv_id, resinfo: parsed_resinfo }
    end
  end
end
